package base

import (
	"context"
	"fmt"
	"github.com/redis/go-redis/v9"
	"github.com/robfig/cron/v3"
	"github.com/zeromicro/go-zero/core/logc"
	"payme3000/framework/errorx"
	frameworkRecover "payme3000/framework/recover"
	"sync"
	"time"
)

const (
	RunStatusWaiting RunStatus = 0 // 等待调度
	RunStatusRunning RunStatus = 1 // 执行中

	MutexCheckerCacheKey              = "cronjob:mutex:checker" // 互斥检查器缓存key
	MutexCheckerCacheExpiration int64 = 600                     // 互斥检查器缓存过期时间, 单位秒
)

type (
	// ICronJob 定时任务
	ICronJob interface {
		IComponent
		RegisterChecker() []ICronJobChecker // 检查器
		Spec() string                       // 调度计划
		Run(context.Context)                // 任务回调, 即实际的任务逻辑所在

		getKey() string           // 返回key
		getEntryId() cron.EntryID // 返回entryId
		getCron() *cron.Cron      // 返回调度器

		setEntryId(d cron.EntryID) // 设置entryId
		setCron(*cron.Cron)        // 设置调度器驱动

		schedule(context.Context, func(context.Context)) // 调度任务

		placeholderCronJob()
	}

	// CronJob ICronJob的基础实现
	CronJob struct {
		Component

		// config
		Key       string // 唯一标识
		IsOverlap bool   // 是否可以重叠执行, 即前一个任务执行持续到下一个调度时间点时还未结束时, 是否允许再次调度任务(即此时有多个任务正在执行), 默认不允许

		Scheduler cron.Schedule // 调度器驱动
		Status    RunStatus     // 运行状态

		checkerRegister []ICronJobChecker
		entryId         cron.EntryID // 调度器返回的entryId, 只读
		cron            *cron.Cron   // 调度器, 只读

		isSuspend bool // 是否暂停调度
		lock      sync.Mutex
	}

	// RunStatus 运行状态
	RunStatus int32
)

func (j *CronJob) Start() error {
	// 初始化调度器驱动
	sc, err := cron.ParseStandard(j.Spec())
	if err != nil {
		return errorx.Throw(j.Ctx, err)
	}

	j.Scheduler = sc

	// 启动检查器
	j.checkerRegister = j.RegisterChecker()
	for _, c := range j.checkerRegister {
		c.Init(j.Ctx)
		_ = c.Start()
		c.SetCronJob(j)
	}

	return nil
}

func (j *CronJob) Suspend() {
	j.isSuspend = true
}

func (j *CronJob) Resume() {
	j.isSuspend = false
}

func (j *CronJob) RegisterChecker() []ICronJobChecker {
	return nil
}

func (j *CronJob) Spec() string {
	return "* * * * *"
}

// Run 由继承CronJob的自定义CronJob各自实现
func (j *CronJob) Run(_ context.Context) {}

func (j *CronJob) getKey() string {
	return j.Key
}

func (j *CronJob) getEntryId() cron.EntryID {
	return j.entryId
}

func (j *CronJob) getCron() *cron.Cron {
	return j.cron
}

func (j *CronJob) setEntryId(id cron.EntryID) {
	j.entryId = id
}

func (j *CronJob) setCron(c *cron.Cron) {
	j.cron = c
}

// schedule 调度任务, 调度时间到点就会执行该方法
func (j *CronJob) schedule(ctx context.Context, job func(context.Context)) {
	j.lock.Lock()
	defer j.lock.Unlock()
	defer j.AddDone()()

	// 不允许重叠执行
	if j.Status == RunStatusRunning && !j.IsOverlap {
		return
	}

	for _, c := range j.checkerRegister {
		if !c.Check() {
			return
		}
	}

	startTime := time.Now().UnixMilli()

	// 设置任务状态
	j.Status = RunStatusRunning

	// 执行任务, 要确保回调即使panic也不会影响服务
	frameworkRecover.WithRecover(func() {
		job(ctx)
	})

	// 完成后重置任务状态
	j.Status = RunStatusWaiting
	endTime := time.Now().UnixMilli()

	logc.Infof(ctx, "cronjob has been run with key:%s duration:%d(ms)", j.Key, endTime-startTime)

	return
}

func (j *CronJob) placeholderCronJob() {}

type (
	ICronJobChecker interface {
		IComponent

		SetCronJob(ICronJob)
		Check() bool
	}

	// checker 检查器默认实现
	checker struct {
		Component

		cronjob ICronJob
	}

	// MutexChecker 互斥检查器
	MutexChecker struct {
		checker

		redisClient redis.UniversalClient
	}
)

func (c *checker) SetCronJob(j ICronJob) {
	c.cronjob = j
}

func NewMutexChecker(redisClient redis.UniversalClient) *MutexChecker {
	return &MutexChecker{
		redisClient: redisClient,
	}
}

func (c *MutexChecker) Check() bool {
	key := fmt.Sprintf(
		"%s:%s:%s",
		MutexCheckerCacheKey,
		c.cronjob.getKey(),
		c.cronjob.getCron().Entry(c.cronjob.getEntryId()).Prev.Format("200601021504"),
	)

	ok, err := c.redisClient.SetNX(
		context.Background(),
		key,
		time.Now().Unix(),
		time.Duration(MutexCheckerCacheExpiration)*time.Second,
	).Result()
	if err != nil {
		errorx.Catch(context.Background(), err)
		return false
	} else if !ok {
		logc.Infof(context.Background(), "cronjob mutex checker flase with key:%s", key)
		return false
	}

	logc.Infof(context.Background(), "cronjob mutex checker true with key:%s", key)

	return true
}
